package framework.util.queryFilter.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.collect.Lists;
import framework.common.Common;
import framework.util._BeanUtil;
import framework.util.layui._LayuiUtil;
import framework.util.queryFilter.annotation.JavabeanField;
import framework.util.queryFilter.dao.IBaseDao;
import framework.util.queryFilter.service.IBaseService;
import framework.util.queryFilter.vo.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;

/**
 * @author 吴宇亮 on 2019/2/7 0007 17:53
 */
public class IBaseServiceImpl<BoType, LidType> implements IBaseService<BoType, LidType> {

    public static final transient Logger log = LoggerFactory.getLogger(IBaseServiceImpl.class);

    protected final Class<BoType> persistType = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    /** 获取该Bo对应的表名 **/
    private final String tableName = persistType.getSimpleName().substring(0, 1).toLowerCase() + _BeanUtil.transformUpperCase2_(persistType.getSimpleName().substring(1));
    /** 缓存起来用来高效率反射 **/
    private final MethodAccess methodAccess = MethodAccess.get(persistType);
    /** 将该BoType的字段缓存起来 **/
    private List<_Field> fieldList = null;

    {
        //初始化每个Bo的字段列表
        Field[] declaredFields = persistType.getDeclaredFields();
        fieldList = new ArrayList<>(declaredFields.length);

        for (Field declaredField : declaredFields) {
            if (!declaredField.isAnnotationPresent(JavabeanField.class)) {
                continue; //只加载映射数据库的属性
            }
            String fieldName = declaredField.getName();
            String type = QueryFilter.fieldTypeMap.get(declaredField.getType());
            fieldList.add(new _Field(fieldName, _BeanUtil.transformUpperCase2_(fieldName), type));
        }
    }

    @Autowired
    private IBaseDao<BoType, LidType> dao;


    @Override
    public Class<BoType> getPersistType() {
        return persistType;
    }

    /**
     * 注意, mybatis的接口不能重载, 所以批量操作用Batch后缀
     */
    @Transactional(rollbackFor={Exception.class})
    @Override
    public void add(BoType bo){
        this.addBatch(Lists.newArrayList(bo));
    }

    /**
     * 注意
     * 1.对于插入的bo主键要为null
     * 2.主键一定要是 叫lid 和 自增的
     */
    @Transactional(rollbackFor={Exception.class})
    @Override
    public void addBatch(List<BoType> list) {
        if(CollectionUtils.isEmpty(list)) {
            return;
        }

        try {
            //将要插入的bo用Insert包起来, 为了解决回传id的问题
            List<Insert<BoType>> insertList = new ArrayList<>(list.size());
            list.forEach(bo -> insertList.add(new Insert<>(bo)));
            Insert<BoType> bo = insertList.get(0);
            bo.setTableName(tableName);
            bo.setFieldList(fieldList);
            bo.generatorSql(null);

            //真正的调用mybatis插入数据
            dao.addBatch(insertList);

            //将lid回传给真正的bo
            for (Insert<BoType> insert : insertList) {
                methodAccess.invoke(insert.getBo(), "setLid", insert.getLid());
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void updateBatch(List<BoType> list){
        list.forEach(this::update);
    }

    @Override
    public void merge(BoType bo) {
        Object lid = methodAccess.invoke(bo, "getLid");
        if(null == lid){
            this.add(bo);
        }else{
            this.update(bo);
        }
    }

    @Transactional
    @Override
    public void mergeBatch(List<BoType> list) {
        List addList = new ArrayList(list.size());
        List updateList = new ArrayList(list.size());
        for (BoType bo : list) {
            Object lid = methodAccess.invoke(bo, "getLid");
            if(null == lid){
                addList.add(bo);
            }else{
                updateList.add(bo);
            }
        }
        this.addBatch(addList);
        this.updateBatch(updateList);
    }

    /**
     * 修改单个bo, 主键不能为空. 修改是不能批量操作的, 所以底层这里用单个修改来实现的
     */
    @Override
    public void update(BoType bo) {
        Object lid = methodAccess.invoke(bo, "getLid");
        if(null == lid){
            throw new NullPointerException("即将被修改的数据主键不能为空");
        }

        try {
            Update<BoType> update = new Update<>();
            update.setTableName(tableName);
            update.setFieldList(fieldList);
            update.setBo(bo);
            update.generatorSql(null);

            dao.update(update, bo);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void removeByQueryFilter(QueryFilter filter) {
        filter.setTableName(tableName);
        filter.setFieldList(fieldList);
        filter.getWhereList().forEach(item -> {
            try {
                item.generatorSql(persistType);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        });

        dao.removeByQueryFilter(filter, filter.getParamMap());
    }

    /**
     * 删除bo, bo的主键不能为空
     * @param bo javaBean
     */
    @Override
    public void remove(BoType bo) {
        this.removeBatch(Lists.newArrayList(bo));
    }

    /**
     * 根据主键删除记录
     * @param lid 主键
     */
    @Override
    public void removeById(LidType lid) {
        try {
            BoType bo = persistType.newInstance();
            methodAccess.invoke(bo, "setLid", lid);
            remove(bo);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 批量删除bo, bo的主键不能为空
     */
    @Override
    public void removeBatch(List<BoType> list) {
        if(null == list || list.size() == 0) {
            throw new IllegalArgumentException("准备删除的数据不能为空！");
        }

        //获取将要删除的list的主键, 保存在集合中
        List<LidType> lidList = new ArrayList<>(list.size());
        list.forEach(bo -> {
            Object lid = methodAccess.invoke(bo, "getLid");
            if(null == lid){
                throw new NullPointerException("准备删除的数据主键不能为空！");
            }
            lidList.add((LidType)lid);
        });
        Delete<LidType> delete = new Delete<>();
        delete.setTableName(tableName);
        delete.setLidList(lidList);

        //真正的调用mybatis删除数据
        dao.removeBatch(delete);
    }

    @Override
    public void removeFakeByQueryFilter(QueryFilter filter) {
        List<BoType> list = this.getAll(filter);
        for (BoType bo : list) {
            removeFake(bo);
        }
    }

    @Override
    public void removeFake(BoType bo) {
        methodAccess.invoke(bo, Common.IS_DELETE_SETTER_METHOD_NAME, Common.IS_YEW);
        this.update(bo);
    }

    @Override
    public void removeFakeById(LidType lid) {
        BoType bo = this.get(lid);
        removeFake(bo);
    }

    @Override
    public void removeFakeBatch(List<BoType> list) {
        for (BoType bo : list) {
            removeFake(bo);
        }
    }

    /**
     * @param lidName 主键字段名
     * @param lid 主键
     * @return
     */
    @Override
    public BoType get(String lidName, LidType lid) {
        String sql = "";
        try {
            Class<?> type = persistType.getDeclaredField(lidName).getType();
            if(String.class == type){
                sql = "select * from " + tableName + " where " + lidName + " = #{_q." + lidName + ", jdbcType=VARCHAR}";
            }else if(Integer.class == type){
                sql = "select * from " + tableName + " where " + lidName + " = #{_q." + lidName + ", jdbcType=INTEGER}";
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return null;
        }

        Map<String, Object> paramMap = new HashMap<>(1);
        paramMap.put(lidName, lid);

        BoType bo = null;
        LinkedHashMap boMap = dao.get(sql, paramMap);
        String json = JSONObject.toJSONString(boMap);
        try {
            bo = (BoType) JSONObject.parseObject(json, Class.forName(persistType.getName()));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bo;
    }

    /**
     * 根据主键获得bo
     * @param lid 主键, 默认为'lid'
     */
    @Override
    public BoType get(LidType lid) {
        return get("lid", lid);
    }

    /**
     * 复杂单表查询
     * @param filter 查询条件
     */
    @Override
    public List<BoType> getAll(QueryFilter filter) {
        ///第一步：设置表名和字段
        filter.setTableName(tableName);
        filter.setFieldList(fieldList);

        ///第二步：处理where查询条件
        filter.getWhereList().forEach(item -> {
            try {
                item.generatorSql(persistType);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        });

        ///第三步：处理orderBy排序条件
        filter.getOrderByList().forEach(item -> {
            try {
                item.generatorSql(persistType);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        });

        ///第四步：查询
        List<LinkedHashMap> boMap = dao.getAll(filter, filter.getParamMap());

        ///第五步：处理page和totals
        PageBean pageBean = filter.getPageBean();
        //只有一页，所以totals就是查询返回的数量
        if(pageBean.getPage() == _LayuiUtil.FIRST_PAGE && pageBean.getSize() == Integer.MAX_VALUE){
            pageBean.setTotals(boMap.size());
        //如果开启了获取总条数
        }else if(pageBean.getIsGetTotals()){
            pageBean.setTotals(dao.getTotals(filter, filter.getParamMap()));
        }

        ///第六步：返回结果
        //即使json是下划线风格，字段命名是骆驼峰风格，fastjson也能匹配到并且赋值。参考博客：https://cloud.tencent.com/info/af5a6b43d1745f97a5eb25d8d72f4bea.html
        String json = JSONObject.toJSONString(boMap);
        return JSONObject.parseArray(json, persistType);
    }

    @Override
    public List<BoType> getAll() {
        return getAll(new QueryFilter(_LayuiUtil.FIRST_PAGE, Integer.MAX_VALUE));
    }

    @Override
    public BoType get(QueryFilter filter) {
        List<BoType> list = this.getAll(filter);
        return CollectionUtils.isEmpty(list) ? null : list.get(0);
    }

    @Override
    public int getCount() {
        return getCount(new QueryFilter(_LayuiUtil.FIRST_PAGE, Integer.MAX_VALUE));
    }

    @Override
    public int getCount(QueryFilter filter) {
        filter.setTableName(tableName);
        filter.getWhereList().forEach(item -> {
            try {
                item.generatorSql(persistType);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        });
        return dao.getTotals(filter, filter.getParamMap());
    }


}
